package controller

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"

	"gitee.com/haodreams/golib/logs"
	"gitee.com/haodreams/libs/kv"
	"github.com/gin-gonic/gin"
	jsoniter "github.com/json-iterator/go"
	"gorm.io/gorm"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

// Controller 控制器
type Controller struct {
	param *kv.Param

	*gin.Context
	ControllerName string //控制器名称
	ActionName     string //事件名称
	Map            gin.H
}

// Setup 初始化设置
func (m *Controller) Setup(c *gin.Context, controllerName string) (err error) {
	m.param = NewParam(c)
	m.Context = c
	m.Map = gin.H{}
	m.ControllerName = controllerName

	path := m.Param("path")
	path = strings.TrimPrefix(path, "/")
	path = strings.ToLower(path)
	ss := strings.Split(path, "/")
	switch len(ss) {
	case 0:
		m.ActionName = "index.html"
	default:
		m.ActionName = ss[0]
		if !strings.HasSuffix(m.ActionName, ".html") {
			m.ActionName += ".html"
		}
	}
	return
}

// GetPagePosition 获取合理的位置 end = 0 表示无效
func (m *Controller) GetPagePosition(total int) (begin, end int) {
	page, limit := m.GetPage()
	begin = (page - 1) * limit
	end = page * limit
	if begin >= total {
		end = 0
		return
	}
	if end > total {
		end = total
	}
	return
}

// GetParam 获取参数属性
func (m *Controller) GetParam() *kv.Param {

	return m.param
}

// Display 显示
func (m *Controller) Display(url ...string) {
	path := ""
	if len(url) > 0 {
		path = strings.Join(url, "/")
	} else {
		path = m.ControllerName + "/" + m.ActionName
	}

	defer func() {
		if info := recover(); info != nil {
			m.Error(fmt.Sprint(info))
			logs.Error(info)
		}
	}()
	//log.Println(path)
	m.HTML(http.StatusOK, path, m.Map)
}

// AddCookie = SetCookie
func (m *Controller) AddCookie(name, value string, maxAge int) {
	m.SetCookie(name, value, maxAge, "/", "", false, true)
}

// Msg 正常通知消息
func (m *Controller) Msg(msg string) {
	m.Map["code"] = http.StatusOK
	m.Map["msg"] = msg
	m.JSON(http.StatusOK, m.Map)
}

// MsgData 正常通知消息
// Deprecated: this function simply calls DataWithCode
func (m *Controller) MsgData(data interface{}) {
	m.Data(data)
}

// MsgData 正常通知消息
func (m *Controller) Data(data interface{}) {
	m.Map["code"] = http.StatusOK
	m.Map["data"] = data
	m.JSON(http.StatusOK, m.Map)
}

// Response 回复json消息
func (m *Controller) Response(data interface{}) {
	m.Map["code"] = http.StatusOK
	m.Map["data"] = data
	m.JSON(http.StatusOK, m.Map)
}

// DataWithMsg 返回数据包
func (m *Controller) DataWithCode(data interface{}, msg string, code ...int) {
	if len(code) > 0 {
		m.Map["code"] = code[0]
	} else {
		m.Map["code"] = http.StatusOK
	}
	m.Map["data"] = data
	m.Map["msg"] = msg
	m.JSON(http.StatusOK, m.Map)
}

// DataWithMsg 返回数据包,并指定code为返回状态码
func (m *Controller) DataWithStatusCode(data interface{}, msg string, codes ...int) {
	code := http.StatusOK
	if len(codes) > 0 {
		code = codes[0]
	}
	m.Map["code"] = code
	m.Map["data"] = data
	m.Map["msg"] = msg
	m.JSON(code, m.Map)
}

// DataWithMsg 返回数据包
// Deprecated: this function simply calls DataWithCode
func (m *Controller) DataWithMsg(data interface{}, msg string, code ...int) {
	m.DataWithCode(data, msg, code...)
}

func (m *Controller) JSONText(data string, codes ...int) {
	code := 200
	if len(codes) > 0 {
		code = codes[0]
	}
	m.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
	m.Writer.WriteString(fmt.Sprintf(`{"code": %d, "data":%s}`, code, data))
}

// Error 错误通知消息
func (m *Controller) Error(msg string) {
	m.Map["code"] = http.StatusPreconditionFailed
	m.Map["msg"] = msg
	m.JSON(http.StatusOK, m.Map)
}

// Error 错误通知消息
func (m *Controller) ErrorBad(msg string) {
	m.Map["code"] = http.StatusPreconditionFailed
	m.Map["msg"] = msg
	m.JSON(http.StatusBadRequest, m.Map)
}

func (m *Controller) JSON(code int, obj any) {
	data, err := json.Marshal(obj)
	if err != nil {
		panic(err)
	}
	m.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
	m.Writer.Write(data)
}

// Rows ...
// {"code":500,"count":0,"data":[], "msg":"` + emsg + `"}`))
func (m *Controller) Rows(count int, rows interface{}, msg string, code ...int) {
	c := http.StatusOK
	if len(code) > 0 {
		c = code[0]
	}

	m.Map["count"] = count
	m.Map["data"] = rows
	m.Map["code"] = c
	m.Map["msg"] = msg
	m.JSON(c, m.Map)
}

// IsPost 是否是post方法提交
func (m *Controller) IsPost() bool {
	return m.Request.Method == "POST"
}

// ErrorWithCode ...
func (m *Controller) ErrorWithCode(msg string, code int) {
	m.JSON(code, gin.H{
		"code": code,
		"msg":  msg,
	})
}

// GetPage 获取分页位置
func (m *Controller) GetPage() (page, limit int) {
	return m.GetPageFromMap(m.GetParam().GetMap())
}

// GetPageFromMap .
func (m *Controller) GetPageFromMap(mp map[string]string) (page, limit int) {
	val := mp["page"]
	page, err := strconv.Atoi(val)
	if err != nil {
		page = 1
	}

	val = mp["limit"]
	limit, err = strconv.Atoi(val)
	if err != nil {
		limit = 50
	}

	if limit < 1 {
		limit = 1
	}

	if page < 1 {
		page = 1
	}
	return
}

// QueryDB 查询数据库
// 根据结构体自动生成
// out 是结构体数组的指针 users=make([]*User, 0) out=&users
func (m *Controller) QueryDB(db *gorm.DB, out interface{}, where string) {
	page, limit := m.GetPage()
	first := limit * (page - 1)
	porders := m.param.GetTrimString("orders")
	var orders []string
	if len(porders) > 0 {
		orders = strings.Split(porders, ",")
	}
	handleQuery(m.Writer, db, first, limit, orders, out, where)
	//dbquery.HandleQuery(m.Writer, db, first, limit, orders, out, where...)
}

// HandleQuery 处理查询
// 自动从数据库中查询需要的数据结构
// out 是结构体数组的指针
func handleQuery(w http.ResponseWriter, db *gorm.DB, first, limit int, orders []string, out interface{}, where string) {
	var err error
	//db.Where(where).Limit(limit, first).OrderBy
	//最多只判断3个order
	total := int64(0)
	v := reflect.ValueOf(out)
	for v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}
	if v.Kind() != reflect.Slice {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Write([]byte(`{"code":500,"count":0,"data":[], "msg":"Not slice,` + v.Kind().String() + `"}`))
		return
	}
	t := reflect.TypeOf(out).Elem()

	o := reflect.New(t)

	err = db.Model(o).Where(where).Count(&total).Error
	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Write([]byte(`{"code":500,"count":0,"data":[], "msg":"` + err.Error() + `"}`))
		return
	}

	switch len(orders) {
	case 0:
		err = db.Where(where).Offset(first).Limit(limit).Find(out).Error
	case 1:
		err = db.Where(where).Offset(first).Limit(limit).Order(orders[0]).Find(out).Error
	default:
		err = db.Where(where).Offset(first).Limit(limit).Order(orders[0]).Order(orders[1]).Find(out).Error
	}

	if err != nil {
		logs.Warn(err.Error())
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		emsg := strings.ReplaceAll(err.Error(), "\"", `\"`)
		w.Write([]byte(`{"code":500,"count":0,"data":[], "msg":"` + emsg + `"}`))
		return
	}

	if total == 0 {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Write([]byte(`{"code":200,"count":0,"data":[]}`))
		return
	}

	data := []byte(`{"code":200,"count":`)
	s := fmt.Sprintf("%d,\"data\":", total)
	data = append(data, []byte(s)...)
	b, err := json.Marshal(out)
	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Write([]byte(`{"code":500,"count":0,"data":[], "msg":"Marshal json error,` + v.Kind().String() + `"}`))
		return
	}
	data = append(data, b...)
	data = append(data, byte('}'))
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)
}

// Page 显示指定页面的数据
// array 传入指定的数组
func (m *Controller) Page(array any, callback func(any) any) {
	page, limit := m.GetPage()

	v := reflect.Indirect(reflect.ValueOf(array))
	if v.Kind() != reflect.Slice {
		m.Rows(0, nil, "No data")
		return
	}

	begin := (page - 1) * limit
	end := page * limit
	n := v.Len()
	if end <= n {
		if callback == nil {
			m.Rows(n, v.Slice(begin, end).Interface(), "OK")
		} else {
			m.Rows(n, callback(v.Slice(begin, end).Interface()), "OK")
		}
	} else {
		end = n
		if begin >= n {
			m.Rows(n, nil, "OK")
		} else {
			if callback == nil {
				m.Rows(n, v.Slice(begin, n).Interface(), "OK")
			} else {
				m.Rows(n, callback(v.Slice(begin, n).Interface()), "OK")
			}
		}
	}
}

// PageMapJSON .
func (m *Controller) PageMapJSON(array interface{}, mp map[string]string, callback func(interface{}) interface{}) ([]byte, error) {
	page, limit := m.GetPageFromMap(mp)
	v := reflect.Indirect(reflect.ValueOf(array))
	if v.Kind() != reflect.Slice {
		return m.RowsJSON(0, nil, "No data")
	}

	begin := (page - 1) * limit
	end := page * limit
	n := v.Len()
	if end <= n {
		if callback == nil {
			return m.RowsJSON(n, v.Slice(begin, end).Interface(), "OK")
		}
		return m.RowsJSON(n, callback(v.Slice(begin, end).Interface()), "OK")
	}
	end = n
	if begin >= n {
		return m.RowsJSON(n, nil, "OK")
	}
	if callback == nil {
		return m.RowsJSON(n, v.Slice(begin, n).Interface(), "OK")
	}
	return m.RowsJSON(n, callback(v.Slice(begin, n).Interface()), "OK")
}

// RowsJSON .
func (m *Controller) RowsJSON(count int, rows interface{}, msg string, code ...int) ([]byte, error) {
	c := http.StatusOK
	if len(code) > 0 {
		c = code[0]
	}
	m.Map["cmd"] = "table"
	m.Map["count"] = count
	m.Map["data"] = rows
	m.Map["code"] = c
	m.Map["msg"] = msg
	return json.Marshal(m.Map)
}

func (c *Controller) RecvFile(path string) (size int64, fileName string, err error) {
	header, err := c.FormFile("file")
	if err != nil {
		c.Error(err.Error())
		return
	}
	dst := header.Filename
	fileName = filepath.Base(dst)
	f, err := header.Open()
	if err != nil {
		c.Error(err.Error())
		return
	}

	defer f.Close()

	tempPath := path + "/temp/"
	if _, err := os.Stat(tempPath); err != nil {
		os.MkdirAll(tempPath, 0755)
	}

	lf, err := os.Create(tempPath + fileName)
	if err != nil {
		logs.Error(err.Error())
		c.Error(err.Error())
		return
	}

	size, _ = io.Copy(lf, f)
	lf.Close()

	fileName = tempPath + fileName
	return
}
